#ifndef OBJECT_H
#define OBJECT_H

/*
 * Allow hashing of objects
 * Place this handle as last element of object structs
 */
#include "uthash.h"
#ifndef HASH_HANDLE
#	define HASH_HANDLE UT_hash_handle hh
#endif

typedef signed char byte;
/* typedef unsigned char uchar; */

typedef union Value Value;
typedef struct Object Object;
typedef struct Type Type;
typedef struct String String;
typedef struct Symbol Symbol;
typedef struct Pair Pair;
typedef struct Function Function;

typedef Object *(UnaryFunc)(Object *);
typedef Object *(BinaryFunc)(Object *, Object *);
typedef UnaryFunc EvalFunc;
/* typedef UnaryFunc ToStringFunc; */
typedef BinaryFunc EqualFunc;
typedef const char *(ToCStringFunc)(Object *);

union Value
{
	int num;
	Pair *pair;
	String *str;
	Symbol *sym;
	Function *func;
};

struct Object
{
	Type *type;
	Value value;
	HASH_HANDLE;
};

struct Type
{
	Type *type;
	String *name;
	EqualFunc *equal;
	EvalFunc *eval;
	ToCStringFunc *to_cstr;
/*	ToStringFunc *to_str; */
};

struct Function
{
	Pair *arg_names; /* give/defined arg names */
	Pair *arg_list; /* runtime arg list */
	/* Pair *arg_v; */ /* Parsed straight arg "vector" (list) */
	/* Pair *arg_kw; */ /* Parsed keyword arguments */
	Pair *body;
	/* Code *code; */ /* compiled (virtual?) machine code */
	/* HASH_HANDLE; */
};

/* Miscelleaneous function signatures */
extern Object *cfold_left(BinaryFunc *, Object *, Object *);
extern Object *cfold_left_no_init(BinaryFunc *, Object *);
extern Object *cfold_right(BinaryFunc *, Object *, Object *);

/*
 * C booleans
 * DO NOT use CTRUE for a comparison value! (True is non-zero,
 * technically, so things like (1 == 2) could cause problems.)
 * Use (x != CFALSE) if needed;  CTRUE is only for convenience
 * in returning a non-zero value.
 */
#define CFALSE 0
#define CTRUE 1

/* Convert C bools to Lisp bools and vice-versa */
#define cbool_to_lbool(b) ((b) ? OTRUE : OFALSE)
#define lbool_to_cbool(b) getboolean(b)

/* Other Boolean things */
#define not(o) (!lbool_to_cbool(o))
#define Not(o) (cbool_to_lbool(not(o))

/* Define & init the Nil, Empty List & Boolean objects in the main file */
extern Object Nil_o;
extern Object Empty_o;
extern Object True_o;
extern Object False_o;
#define ONIL (&Nil_o)
#define OEMPTY (&Empty_o)
#define OTRUE (&True_o)
#define OFALSE (&False_o)

/* Type objects */
extern Type Type_t;
extern Type Nil_t;
extern Type Empty_t;
extern Type Boolean_t;
extern Type Number_t;
extern Type String_t;
extern Type Symbol_t;
extern Type Pair_t;
extern Type Function_t;
#define TTYPE (&Type_t)
#define TNIL (&Nil_t)
#define TEMPTY (&Empty_t)
#define TBOOLEAN (&Boolean_t)
#define TNUMBER (&Number_t)
#define TSTRING (&String_t)
#define TSYMBOL (&Symbol_t)
#define TPAIR (&Pair_t)
#define TFUNCTION (&Function_t)

/* Object value access */
#define gettype(o) ((o)->type)
#define getvalue(o) ((o)->value)
#define getnumber(o) (getvalue(o).num)
#define getboolean(o) getnumber(o)
#define getstring(o) (getvalue(o).str)
#define getsymbol(o) (getvalue(o).sym)
#define getpair(o) (getvalue(o).pair)
#define getfunction(o) (getvalue(o).func)

/* Object value mutation */
#define settype(o, t) (gettype(o) = (t))
#define setvalue(o, v) (getvalue(o) = (v))
#define setnumber(o, n) (getnumber(o) = (n))
#define setboolean(o, b) (getboolean(o) = (b))
#define setstring(o, s) (getstring(o) = (s))
#define setsymbol(o, s) (getsymbol(o) = (s))
#define setpair(o, p) (getpair(o) = (p))
#define setfunction(o, f) (getfunction(o) = (f))

/* Type checking */
#define oftype(o, t) (gettype(o) == (t))
#define type_p(o) oftype(o, TTYPE)
#define nil_p(o) oftype(o, TNIL)
#define empty_p(o) oftype(o, TEMPTY)
#define number_p(o) oftype(o, TNUMBER)
#define boolean_p(o) oftype(o, TBOOLEAN)
#define string_p(o) oftype(o, TSTRING)
#define symbol_p(o) oftype(o, TSYMBOL)
#define pair_p(o) oftype(o, TPAIR)
#define function_p(o) oftype(o, TFUNCTION)

#define Type_p(o) cbool_to_lbool(type_p(o))
#define Nil_p(o) cbool_to_lbool(nil_p(o))
#define Empty_p(o) cbool_to_lbool(empty_p(o))
#define Boolean_p(o) cbool_to_lbool(boolean_p(o))
#define Number_p(o) cbool_to_lbool(number_p(o))
#define String_p(o) cbool_to_lbool(string_p(o))
#define Symbol_p(o) cbool_to_lbool(symbol_p(o))
#define Pair_p(o) cbool_to_lbool(pair_p(o))
#define Function_p(o) cbool_to_lbool(function_p(o))

/* List stuff */
/* int list_p(Object *); */
#define list_p(o) (list_length(o) >= 0)
#define List_p(o) cbool_to_lbool(list_p(o))
extern int list_length(Object *);
#define List_length(o) Number_new(list_length(o))

/* Type object methods */
/* Eval */
extern EvalFunc Type_eval;
extern EvalFunc Nil_eval;
extern EvalFunc Empty_eval;
extern EvalFunc Boolean_eval;

/* To-C-String object methods */
extern ToCStringFunc Type_to_cstr;
extern ToCStringFunc Nil_to_cstr;
extern ToCStringFunc Empty_to_cstr;
extern ToCStringFunc Boolean_to_cstr;

/* Equal "equal?" or "eql?" */
/* Comparison other than simple pointer address ("eq?") */
/* extern EqualFunc Equal_p; */ /* actually the generic "equal?" accessor */
extern EqualFunc Type_equal;
extern EqualFunc Nil_equal;
extern EqualFunc Empty_equal;
extern EqualFunc Boolean_equal;

/* simple pointer equality -- same mem obj */
#define eq_p(o1, o2) ((o1) == (o2))
#define Eq_p(o1, o2) cbool_to_lbool(eq_p((o1), (o2)))
#define equal_p(o1, o2) lbool_to_cbool(Equal_p((o1), (o2)))
#define Equal_p(o1, o2) (gettype(o1)->equal((o1), (o2)))
#define Eval(o) (gettype(o)->eval(o))
#define To_cstr(o) (gettype(o)->to_cstr(o))
extern Object *To_lstr(Object *);

#define SELF_EVAL_FUNC(t) \
	Object * t##_eval (Object *o) \
	{ \
		return o; /* self-evaluating */ \
	}

#endif /* !OBJECT_H */
